package com.aabte.commons.encrypt;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/3/29
 */
public abstract class AbstractEncryptor<T extends Key> implements Encryptor<T> {

  @Override
  public String getProviderName() {
    return EncryptConfig.providerName();
  }

  protected byte[] getPrivateKeyBytes() {
    return EncryptConfig.privateKeyContent();
  }

  protected byte[] getPublicKeyBytes() {
    return EncryptConfig.publicKeyContent();
  }

  protected byte[] segmentDecrypt(byte[] data, T key) {
    try {
      Cipher cipher = getCipherInstance();
      cipher.init(Cipher.DECRYPT_MODE, key);
      int blockSize = cipher.getBlockSize();
      ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
      for (int i = 0; i < data.length; i += blockSize) {
        bout.write(cipher.doFinal(data, i * blockSize, blockSize));
      }
      return bout.toByteArray();
    } catch (NoSuchAlgorithmException
        | NoSuchPaddingException
        | InvalidKeyException
        | IOException
        | IllegalBlockSizeException
        | BadPaddingException
        | NoSuchProviderException e) {
      throw new EncryptException(e);
    }
  }

  protected byte[] segmentEncrypt(byte[] data, T key) {
    try {
      Cipher cipher = getCipherInstance();
      cipher.init(Cipher.ENCRYPT_MODE, key);
      // 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
      // 加密块大小为127
      // byte,加密后为128个byte;因此共有2个加密块，第一个127
      // byte第二个为1个byte
      int blockSize = cipher.getBlockSize();
      // 获得加密块加密后块大小
      int outputSize = cipher.getOutputSize(data.length);
      int leavedSize = data.length % blockSize;
      int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
      byte[] cipherText = new byte[outputSize * blocksSize];
      int i = 0;
      while (data.length - i * blockSize > 0) {
        // 这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到
        // ByteArrayOutputStream中，而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了
        // OutputSize所以只好用dofinal方法。
        if (data.length - i * blockSize > blockSize) {
          cipher.doFinal(data, i * blockSize, blockSize, cipherText, i * outputSize);
        } else {
          cipher.doFinal(
              data, i * blockSize, data.length - i * blockSize, cipherText, i * outputSize);
        }
        ++i;
      }
      return cipherText;
    } catch (NoSuchAlgorithmException
        | NoSuchPaddingException
        | InvalidKeyException
        | BadPaddingException
        | IllegalBlockSizeException
        | ShortBufferException
        | NoSuchProviderException e) {
      throw new EncryptException(e);
    }
  }

  private Cipher getCipherInstance()
      throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
    String providerName = getProviderName();
    if (null == providerName || providerName.length() == 0) {
      return Cipher.getInstance(getAlgorithm());
    }

    try {
      Class<?> aClass = AbstractEncryptor.class.getClassLoader().loadClass(getProviderName());
      return Cipher.getInstance(getAlgorithm(), (Provider) aClass.newInstance());
    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
      return Cipher.getInstance(getAlgorithm(), getProviderName());
    }
  }
}
